Skip to content

[中文版本](/posts/c教程/zh-CN/06-编译与构建/051-From Source to Executable The Compilation Pipeline)

Every time you run

gcc hello.c -o hello

you’re launching a complex, multi-stage process that transforms human-readable C code into a machine-executable binary. Understanding this compilation pipeline is the heart of becoming a real systems programmer.

Let’s unpack what happens between your.c file and the final executable.

Step 1. The Four Stages ​

A C compiler (likegcc orclang) performs four major stages internally:

StageToolInputOutputDescription
1. Preprocessingcpphello.cexpanded sourceHandles#include,#define, and macros
2. Compilationcc1expanded sourcehello.sTranslates C into assembly
3. Assemblyashello.shello.oConverts assembly into machine code
4. Linkingld.o+ librarieshelloCombines everything into a runnable program

You can stop at any step with compiler flags to see what’s happening.

Tiny Code: Observe Each Stage ​

Let’s use a simple program:

// hello.c
#include <stdio.h>
int main(void) {
    printf("Hello, C!\n");
    return 0;
}

Run these commands to inspect each stage:

# 1. Preprocessing
gcc -E hello.c -o hello.i
# 2. Compilation to Assembly
gcc -S hello.i -o hello.s
# 3. Assembly to Object File
gcc -c hello.s -o hello.o
# 4. Linking
gcc hello.o -o hello

Now check what each file looks like:

  • hello.i: C code with headers expanded
  • hello.s: Human-readable assembly instructions
  • hello.o: Machine code (binary object)
  • hello: Final executable

Run:

./hello

Output:

Hello, C!

Step 2. Preprocessing (#include, #define, #if) ​

The preprocessor handles all lines starting with#. It’s purely textual, no code execution yet.

gcc -E hello.c -o hello.i

Openhello.i and you’ll see thousands of lines fromstdio.h inserted into your code. It also replaces macros and removes comments.

This is the stage where your headers, macros, and conditional compilation come to life.

Step 3. Compilation (to Assembly) ​

Next, the compiler translates your preprocessed C code into assembly language for your target CPU.

gcc -S hello.i -o hello.s

Openhello.s to peek at low-level instructions like:

mov     edi, OFFSET FLAT:.LC0
call    puts

These are CPU-specific, on x86, ARM, or RISC-V, they’ll differ. This stage also performs optimization, type checking, and error detection.

Step 4. Assembly (to Object File) ​

The assembler converts the.s file into raw machine instructions and data structures, producing a relocatable object file.

gcc -c hello.s -o hello.o

You can inspect it with:

objdump -d hello.o

Each function in your code becomes a symbol in this object file.

Step 5. Linking ​

The linker (ld) combines object files and libraries into a single executable.

gcc hello.o -o hello

If your program uses external functions (likeprintf), the linker locates them in system libraries (e.g.,/usr/lib/libc.so) and records their addresses.

The result: one self-contained executable ready to run.

Step 6. Inspect the Final Binary ​

Use tools like:

file hello
nm hello | head
readelf -h hello

These reveal:

  • File type (ELF, Mach-O, etc.)
  • Defined and undefined symbols
  • Sections like.text,.data,.bss

You’re now seeing your C code at the machine level.

Step 7. Cleanup and Automation ​

While it’s educational to run each step manually, most of the time you’ll rely on:

gcc hello.c -o hello

or aMakefile to orchestrate multiple files (we’ll cover that in Section 55).

Tiny Code: Multi-File Example ​

Let’s build a two-file program manually:

main.c

#include "greet.h"
int main(void) {
    greet("C programmer");
    return 0;
}

greet.c

#include <stdio.h>
void greet(const char *name) {
    printf("Hello, %s!\n", name);
}

greet.h

void greet(const char *name);

Compile and link manually:

gcc -c main.c
gcc -c greet.c
gcc main.o greet.o -o app
./app

Output:

Hello, C programmer!

Why It Matters ​

Understanding the compilation pipeline helps you:

  • Debug tricky build errors (undefined reference,multiple definition, etc.)
  • Control optimization and debugging symbols
  • Inspect intermediate stages for learning or tuning performance
  • Build your own lightweight build systems or compilers

This is how source code becomes machine reality, step by step, precisely defined.

Try It Yourself ​

  1. Generate all intermediate files (.i,.s,.o) for a few programs and inspect them.
  2. Experiment withgcc -O0,-O2, and-O3 and observe how assembly changes.
  3. Add-g and explore the binary withgdb.
  4. Build a program that spans multiple.c files.
  5. Usenm andobjdump to trace how symbols move through the stages.

Next, you’ll explore the preprocessor and macros, the engine behind includes, constants, and compile-time code generation.

本站总访问 — 次